import type { INestApplication } from "@nestjs/common";
import type { OpenApi } from "@samchon/openapi";

/**
 * Definition for the `nestia.config.ts` file.
 *
 * @author Jeongho Nam - https://github.com/samchon
 */
export interface INestiaConfig {
  /**
   * Accessor of controller classes.
   *
   * You can specify target controller classes within two ways.
   *
   * - Asynchronous function returning `INestApplication` instance
   * - Specify the path or directory of controller class files
   */
  input:
    | (() => Promise<INestApplication>)
    | INestiaConfig.IInput
    | string[]
    | string;

  /**
   * Building `swagger.json` is also possible.
   *
   * If not specified, you can't build the `swagger.json`.
   */
  swagger?: INestiaConfig.ISwaggerConfig;

  /**
   * Response directory that SDK would be placed in.
   *
   * If not configured, you can't build the SDK library.
   */
  output?: string;

  /**
   * Target directory that SDK distribution files would be placed in.
   *
   * If you configure this property and runs `npx nestia sdk` command,
   * distribution environments for the SDK library would be generated.
   *
   * After the SDK library generation, move to the `distribute` directory, and
   * runs `npm publish` command, then you can share SDK library with other
   * client (frontend) developers.
   *
   * Recommend to use `"packages/api"` value.
   */
  distribute?: string;

  /** @default false */
  keyword?: boolean;

  /**
   * Allow simulation mode.
   *
   * If you configure this property to be `true`, the SDK library would be
   * contain simulation mode. In the simulation mode, the SDK library would not
   * communicate with the real backend server, but just returns random mock-up
   * data with requestion data validation.
   *
   * For reference, random mock-up data would be generated by
   * `typia.random<T>()` function.
   *
   * @default false
   */
  simulate?: boolean;

  /**
   * Target directory that e2e test functions would be placed in.
   *
   * If you configure this property and runs `npx nestia e2e` command,
   * `@nestia/sdk` will analyze your NestJS backend server code, and generates
   * e2e test functions for every API endpoints.
   *
   * If not configured, you can't run `npx nestia e2e` command.
   */
  e2e?: string;

  /**
   * Whether to use propagation mode or not.
   *
   * If being configured, interaction functions of the SDK library would perform
   * the propagation mode. The propagation mode means that never throwing
   * exception even when status code is not 200 (or 201), but just returning the
   * {@link IPropagation} typed instance, which can specify its body type through
   * discriminated union determined by status code.
   *
   * @default false
   */
  propagate?: boolean;

  /**
   * Whether to clone DTO structures or not.
   *
   * If being configured, all of DTOs used in the backend server would be cloned
   * into the `structures` directory, and the SDK library would be refer to the
   * cloned DTOs instead of the original.
   *
   * @default false
   */
  clone?: boolean;

  /**
   * Whether to wrap DTO by primitive type.
   *
   * If you don't configure this property as `false`, all of DTOs in the SDK
   * library would be automatically wrapped by {@link Primitive} type.
   *
   * For refenrece, if a DTO type be capsuled by the {@link Primitive} type, all
   * of methods in the DTO type would be automatically erased. Also, if the DTO
   * has a `toJSON()` method, the DTO type would be automatically converted to
   * return type of the `toJSON()` method.
   *
   * @default true
   */
  primitive?: boolean;

  /**
   * Whether to assert parameter types or not.
   *
   * If you configure this property to be `true`, all of the function parameters
   * of SDK library would be checked through [`typia.assert<T>()`
   * function](https://typia.io/docs/validators/assert/).
   *
   * This option would make your SDK library compilation time a little bit
   * slower, but would enahcne the type safety even in the runtime level.
   *
   * @default false
   */
  assert?: boolean;

  /**
   * Whether to optimize JSON string conversion 10x faster or not.
   *
   * If you configure this property to be `true`, the SDK library would utilize
   * the [`typia.assertStringify<T>()
   * function`](https://github.com/samchon/typia#enhanced-json) to boost up JSON
   * serialization speed and ensure type safety.
   *
   * This option would make your SDK library compilation time a little bit
   * slower, but would enhance JSON serialization speed 10x faster. Also, it can
   * ensure type safety even in the runtime level.
   *
   * @default false
   */
  json?: boolean;
}
export namespace INestiaConfig {
  /**
   * List of files or directories to include or exclude to specifying the NestJS
   * controllers.
   */
  export interface IInput {
    /** List of files or directories containing the NestJS controller classes. */
    include: string[];

    /** List of files or directories to be excluded. */
    exclude?: string[];
  }

  /** Building `swagger.json` is also possible. */
  export interface ISwaggerConfig {
    /**
     * Response path of the `swagger.json`.
     *
     * If you've configured only directory, the file name would be the
     * `swagger.json`. Otherwise you've configured the full path with file name
     * and extension, the `swagger.json` file would be renamed to it.
     */
    output: string;

    /**
     * OpenAPI version.
     *
     * If you configure this property to be `2.0` or `3.0`, the newly generated
     * `swagger.json` file would follow the specified OpenAPI version. The newly
     * generated `swagger.json` file would be downgraded from the OpenAPI v3.1
     * specification by {@link OpenApi.downgrade} method.
     *
     * @default 3.1
     */
    openapi?: "2.0" | "3.0" | "3.1";

    /**
     * Whether to beautify JSON content or not.
     *
     * If you configure this property to be `true`, the `swagger.json` file
     * would be beautified with indentation (2 spaces) and line breaks. If you
     * configure numeric value instead, the indentation would be specified by
     * the number.
     *
     * @default false
     */
    beautify?: boolean | number;

    /**
     * Whether to include additional information or not.
     *
     * If configured to be `true`, those properties would be added into each API
     * endpoinnt.
     *
     * - `x-nestia-method`
     * - `x-nestia-namespace` ` `x-nestia-jsDocTags`
     *
     * @default false
     */
    additional?: boolean;

    /**
     * API information.
     *
     * If omitted, `package.json` content would be used instead.
     */
    info?: Partial<OpenApi.IDocument.IInfo>;

    /** List of server addresses. */
    servers?: OpenApi.IServer[];

    /**
     * Security schemes.
     *
     * When generating `swagger.json` file through `nestia`, if your controllers
     * or theirs methods have a security key which is not enrolled in here
     * property, it would be an error.
     */
    security?: Record<string, OpenApi.ISecurityScheme>;

    /**
     * List of tag names with description.
     *
     * It is possible to omit this property or skip some tag name even if the
     * tag name is used in the API routes. In that case, the tag name would be
     * used without description.
     *
     * Of course, if you've written a comment like `@tag {name} {description}`,
     * you can entirely replace this property specification.
     */
    tags?: OpenApi.IDocument.ITag[];

    /**
     * Decompose query DTO.
     *
     * If you configure this property to be `true`, the query DTO would be
     * decomposed into individual query parameters per each property. Otherwise
     * you set it to be `false`, the query DTO would be one object type which
     * contains all of query parameters.
     *
     * @default true
     */
    decompose?: boolean;

    /**
     * Operation ID generator.
     *
     * @param props Properties of the API endpoint.
     * @returns Operation ID.
     */
    operationId?(props: {
      class: string;
      function: string;
      method: "HEAD" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
      path: string;
    }): string;
  }
}
